package com.wordplat.quickstart.xutils.image;

import com.wordplat.quickstart.xutils.cache.DiskCacheEntity;
import com.wordplat.quickstart.xutils.cache.DiskCacheFile;
import com.wordplat.quickstart.xutils.cache.LruDiskCache;
import com.wordplat.quickstart.xutils.common.Callback;
import com.wordplat.quickstart.xutils.common.task.PriorityExecutor;
import com.wordplat.quickstart.xutils.common.util.IOUtil;
import com.wordplat.quickstart.xutils.common.util.LogUtil;

import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.ColorSpace;

import java.io.*;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by wyouflf on 15/10/9.
 * ImageDecoder for ImageLoader
 */
public final class ImageDecoder {
    private static final int BITMAP_DECODE_MAX_WORKER;
    private static final AtomicInteger bitmapDecodeWorker = new AtomicInteger(0);
    private static final Object bitmapDecodeLock = new Object();

    private static final Object gifDecodeLock = new Object();
    private static final byte[] GIF_HEADER = new byte[]{'G', 'I', 'F'};

    private static final Executor THUMB_CACHE_EXECUTOR = new PriorityExecutor(1, true);
    private static final LruDiskCache THUMB_CACHE = LruDiskCache.getDiskCache("xUtils_img_thumb");

    static {
        int cpuCount = Runtime.getRuntime().availableProcessors();
        BITMAP_DECODE_MAX_WORKER = cpuCount > 4 ? 2 : 1;
    }

    private ImageDecoder() {
    }

    static void clearCacheFiles() {
        THUMB_CACHE.clearCacheFiles();
    }

    static PixelMap decodeFileWithLock(final File file,
                                       final ImageOptions options,
                                       final Callback.Cancelable cancelable) throws IOException {
        if (file == null || !file.exists() || file.length() < 1) {
            return null;
        }
        if (cancelable != null && cancelable.isCancelled()) {
            throw new Callback.CancelledException("cancelled during decode image");
        }

        PixelMap result = null;
        PixelMap bitmap = null;
        boolean decodeStarted = false;
        try {
            synchronized (bitmapDecodeLock) {
                while (bitmapDecodeWorker.get() >= BITMAP_DECODE_MAX_WORKER
                        && (cancelable == null || !cancelable.isCancelled())) {
                    try {
                        bitmapDecodeLock.wait();
                    } catch (InterruptedException iex) {
                        throw new Callback.CancelledException("cancelled during decode image");
                    } catch (Throwable ignored) {
                        String ss = ignored.toString();
                    }
                }
            }

            if (cancelable != null && cancelable.isCancelled()) {
                throw new Callback.CancelledException("cancelled during decode image");
            }

            decodeStarted = true;
            bitmapDecodeWorker.incrementAndGet();
            if (options.isCompress()) {
                bitmap = getThumbCache(file, options);
            }
            if (bitmap == null) {
                bitmap = decodeBitmap(file, options, cancelable);
                if (bitmap != null && options.isCompress()) {
                    final PixelMap finalBitmap = bitmap;
                    THUMB_CACHE_EXECUTOR.execute(new Runnable() {
                        @Override
                        public void run() {
                            saveThumbCache(file, options, finalBitmap);
                        }
                    });
                }
            }
        } finally {
            if (decodeStarted) {
                bitmapDecodeWorker.decrementAndGet();
            }
            synchronized (bitmapDecodeLock) {
                bitmapDecodeLock.notifyAll();
            }
        }
        if (bitmap != null) {
            result = bitmap;
        }

        return result;
    }

    /**
     * isGif
     *
     * @param file file
     * @return boolean
     */
    public static boolean isGif(File file) {
        FileInputStream in = null;
        try {
            in = new FileInputStream(file);
            byte[] header = IOUtil.readBytes(in, 0, 3);
            return Arrays.equals(GIF_HEADER, header);
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        } finally {
            IOUtil.closeQuietly(in);
        }

        return false;
    }

    /**
     * decodeBitmap
     *
     * @param file
     * @param options
     * @param cancelable
     * @return PixelMap
     * @throws IOException
     */
    public static PixelMap decodeBitmap(File file, ImageOptions options, Callback.Cancelable cancelable) throws IOException {
        ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
        ImageSource imageSource = ImageSource.create(file, sourceOptions);
        ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
        decodingOptions.sampleSize = calculateSampleSize(
                imageSource.getImageInfo().size.width, imageSource.getImageInfo().size.height,
                options.getMaxWidth(), options.getMaxHeight());
        return imageSource.createPixelmap(decodingOptions);
    }

    /**
     * 计算压缩采样倍数
     *
     * @param rawWidth  图片宽度
     * @param rawHeight 图片高度
     * @param maxWidth  最大宽度
     * @param maxHeight 最大高度
     * @return 压缩采样倍数
     */
    public static int calculateSampleSize(final int rawWidth, final int rawHeight,
                                          final int maxWidth, final int maxHeight) {
        int sampleSize = 1;

        if (rawWidth > maxWidth || rawHeight > maxHeight) {
            if (rawWidth > rawHeight) {
                sampleSize = Math.round((float) rawHeight / (float) maxHeight);
            } else {
                sampleSize = Math.round((float) rawWidth / (float) maxWidth);
            }

            if (sampleSize < 1) {
                sampleSize = 1;
            }

            final float totalPixels = rawWidth * rawHeight;

            final float maxTotalPixels = maxWidth * maxHeight * 2;

            while (totalPixels / (sampleSize * sampleSize) > maxTotalPixels) {
                sampleSize++;
            }
        }
        return sampleSize;
    }

    private static void saveThumbCache(File file, ImageOptions options, PixelMap thumbBitmap) {
        DiskCacheEntity entity = new DiskCacheEntity();
        try {
            entity.setKey(
                    file.getCanonicalPath() + "@" + file.lastModified() + options.toString());
        } catch (IOException e) {
            String ee = e.toString();
        }
        DiskCacheFile cacheFile = null;
        OutputStream out = null;
        try {
            cacheFile = THUMB_CACHE.createDiskCacheFile(entity);
            if (cacheFile != null) {
                out = new FileOutputStream(cacheFile);
                out.flush();
                cacheFile = cacheFile.commit();
            }
        } catch (Throwable ex) {
            IOUtil.deleteFileOrDir(cacheFile);
            LogUtil.w(ex.getMessage(), ex);
        } finally {
            IOUtil.closeQuietly(cacheFile);
            IOUtil.closeQuietly(out);
        }
    }

    private static PixelMap getThumbCache(File file, ImageOptions options) {
        DiskCacheFile cacheFile = null;
        try {
            cacheFile = THUMB_CACHE.getDiskCacheFile(
                    file.getCanonicalPath() + "@" + file.lastModified() + options.toString());
            if (cacheFile != null && cacheFile.exists()) {

                ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
                ImageSource imageSource = ImageSource.create(file, sourceOptions);
                ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
                decodingOptions.desiredColorSpace = ColorSpace.ADOBE_RGB_1998;
                return imageSource.createPixelmap(decodingOptions);
            }
        } catch (Throwable ex) {
            LogUtil.w(ex.getMessage(), ex);
        } finally {
            IOUtil.closeQuietly(cacheFile);
        }
        return null;
    }
}
